package com.spring.proxy.gp;

import java.io.File;
import java.io.FileWriter;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;

import javax.tools.JavaCompiler;
import javax.tools.JavaCompiler.CompilationTask;
import javax.tools.JavaFileObject;
import javax.tools.StandardJavaFileManager;
import javax.tools.ToolProvider;

public class GPProxy {

	public static Object newProxyInstance(GPClassLoader classLoader, Class<?>[] interfaces, GPInvocationHandler h) {
		try {
			StringBuilder javaCode = generate(interfaces[0], h);
			
			// 2、Java文件输出磁盘
			String filePath = GPProxy.class.getResource("").getPath();
			File file = new File(filePath + "$GPProxy0.java");
			FileWriter fw = new FileWriter(file);
			fw.write(javaCode.toString());
			fw.flush();
			fw.close();

			// 编译源码，生成class,注意编译环境要换成jdk才有compiler,单纯的jre没有compiler，会空指针错误
			JavaCompiler jc = ToolProvider.getSystemJavaCompiler();
			// 文件管事器
			StandardJavaFileManager fileMgr = jc.getStandardFileManager(null, null, null);

			// 编译单元
			Iterable<? extends JavaFileObject> units = fileMgr.getJavaFileObjects(file);

			// 编译任务
			CompilationTask t = jc.getTask(null, fileMgr, null, null, null, units);

			// 编译
			t.call();
			fileMgr.close();

			Class<?> proxyClass = classLoader.findClass("$GPProxy0");
			Constructor<?> ctr = proxyClass.getConstructor(GPInvocationHandler.class);
			file.delete();
			return ctr.newInstance(h);
		} catch (Exception e) {
			e.printStackTrace();
		}
		return null;
	}

	private static StringBuilder generate(Class<?> interfaceClazz, GPInvocationHandler h) {
		StringBuilder javaCode = new StringBuilder();
		String packageNameCurr = GPProxy.class.getPackage().getName();
		javaCode.append("package "+packageNameCurr+";");
		javaCode.append("import java.lang.reflect.*;");
		javaCode.append("import com.spring.proxy.gp.GPInvocationHandler;");
		javaCode.append("public class $GPProxy0 implements " + interfaceClazz.getName());
		javaCode.append("{");
		javaCode.append("private GPInvocationHandler h;");
		javaCode.append("public $GPProxy0(GPInvocationHandler h){");
		javaCode.append("this.h=h;");
		javaCode.append("}");
		Method[] methods = interfaceClazz.getMethods();
		for (Method method : methods) {
			javaCode.append(implMethod(method, h, interfaceClazz));
		}
		javaCode.append("}");
		return javaCode;
	}

	private static StringBuilder implMethod(Method method, GPInvocationHandler h, Class<?> interfaceClazz) {
		StringBuilder implMethod = new StringBuilder();
		String returnType = method.getReturnType().getName();
		implMethod.append("public " + returnType + " " + method.getName());
		implMethod.append("(");

		Parameter[] ps = method.getParameters();
		StringBuilder paramArray = new StringBuilder();
		StringBuilder paramType = new StringBuilder(",");
		StringBuilder params = new StringBuilder("Object[] param = new Object[" + ps.length + "];");
		int i = 0;
		for (Parameter parameter : ps) {
			paramType.append(parameter.getType().getName() + ".class" + ",");
			paramArray.append(parameter.getType().getName() + " " + parameter.getName() + ",");
			params.append("param[" + i + "]=" + parameter.getName() + ";");
			i++;
		}
		if (paramType.length() != 0) {
			paramType.deleteCharAt(paramType.length() - 1);
		} else {
			paramType = new StringBuilder();
		}
		StringBuilder meth = new StringBuilder("Method m=null;");

		meth.append("try {" + "			m = " + interfaceClazz.getName() + ".class.getMethod(\"" + method.getName()
				+ "\"" + paramType + ");");
		if (paramArray.length() != 0) {
			paramArray.deleteCharAt(paramArray.length() - 1);
		}
		implMethod.append(paramArray);
		implMethod.append(")");
		implMethod.append("{");
		implMethod.append("Object obj=null;");
		implMethod.append(params);
		implMethod.append(meth);
		if (void.class.getName().equals(returnType)) {
			implMethod.append("h.invoke(this,m, param);");
		} else {
			implMethod.append("obj=h.invoke(this,m, param);");
		}
		implMethod.append("} catch (Throwable e) {" + "			e.printStackTrace();" + "		}");
		if (!void.class.getName().equals(returnType)) {
			implMethod.append("return (" + returnType + ")obj;");
		}
		implMethod.append("}");
		return implMethod;
	}
}
